/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package AuthPenSign.Signature;

import java.util.*;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.math3.analysis.integration.*;
import org.apache.commons.math3.analysis.interpolation.SplineInterpolator;
import org.apache.commons.math3.analysis.polynomials.PolynomialSplineFunction;
import org.apache.commons.math3.linear.*;
import org.apache.commons.math3.stat.descriptive.moment.Mean;
import org.apache.commons.math3.stat.descriptive.rank.Max;
import org.apache.commons.math3.analysis.function.*;

/**
 *
 * @author V-ray
 */
public class Signature {

	private Point[] points;
	private Double maxEigenValue;
//	public double[] eigenValues;

	public Point[] getPoints() {
		return this.points;
	}

	public Double getMaxEigenValue() {
		return this.maxEigenValue;
	}

	public Signature(ArrayList<Point> rawSign) {
		double[][] rawPoints = new double[Point.getSize()][];
		for (int i = 0; i < rawPoints.length; i++) {
			rawPoints[i] = new double[rawSign.size()];
			for (int j = 0; j < rawPoints[i].length; j++) {
				rawPoints[i][j] = rawSign.get(j).get(i);
			}
		}

		double[] center = new double[rawPoints.length];
		for (int i = 0; i < center.length; i++) {
			center[i] = new Mean().evaluate(rawPoints[i]);
		}

		double[][] centeredPoints = new double[rawPoints.length][];
		for (int i = 0; i < centeredPoints.length; i++) {
			centeredPoints[i] = new double[rawPoints[i].length];
			for (int j = 0; j < rawPoints[i].length; j++) {
				centeredPoints[i][j] = rawPoints[i][j] - center[i];
			}
		}

		double[][][] G = new double[centeredPoints.length][centeredPoints.length][];
		for (int i = 0; i < G.length; i++) {
			for (int j = 0; j < G[i].length; j++) {
				G[i][j] = new double[centeredPoints[i].length];
				for (int k = 0; k < G[i][j].length; k++) {
					G[i][j][k] = centeredPoints[i][k] * centeredPoints[j][k];
				}
			}
		}

		double[][] meanG = new double[G.length][];
		for (int i = 0; i < meanG.length; i++) {
			meanG[i] = new double[G[i].length];
			for (int j = 0; j < meanG[i].length; j++) {
				meanG[i][j] = new Mean().evaluate(G[i][j]);
			}
		}

		EigenDecomposition ed = new EigenDecomposition(MatrixUtils.createRealMatrix(meanG));

		double[][] eigenVectors = new double[ed.getEigenvectors().length][];
		for (int i = 0; i < eigenVectors.length; i++) {
			eigenVectors[i] = ed.getEigenvector(i).toArray();
		}

		RealMatrix eigenVectorsMatrix = MatrixUtils.createRealMatrix(eigenVectors);

		double[][] rotatedPoints = new double[rawSign.size()][];
		for (int i = 0; i < rotatedPoints.length; i++) {
			rotatedPoints[i] = new double[centeredPoints.length];
			for (int j = 0; j < rotatedPoints[i].length; j++) {
				rotatedPoints[i][j] = centeredPoints[j][i];
			}
			rotatedPoints[i] = eigenVectorsMatrix.operate(MatrixUtils.createRealVector(rotatedPoints[i])).toArray();
		}

////		this.eigenValues = ed.getRealEigenvalues();
		this.maxEigenValue = new Max().evaluate(ed.getRealEigenvalues());
		double[][] scaledPoints = new double[rawSign.size()][];
		for (int i = 0; i < scaledPoints.length; i++) {
			scaledPoints[i] = new double[centeredPoints.length];
			for (int j = 0; j < scaledPoints[i].length; j++) {
				scaledPoints[i][j] = (rotatedPoints[i][j] / this.maxEigenValue);
				//scaledPoints[i][j] = rotatedPoints[i][j];
//				if (this.eigenValues[j] != 0) {
//					scaledPoints[i][j] = rotatedPoints[i][j] / this.eigenValues[j];
//				}
			}
		}

		this.points = new Point[rawSign.size()];
		for (int i = 0; i < this.points.length; i++) {
			this.points[i] = new Point(rawSign.get(i).time, rotatedPoints[i][0], rotatedPoints[i][1], rotatedPoints[i][2]);
//			this.points[i] = new Point(rawSign.get(i).time, scaledPoints[i][0], scaledPoints[i][1], scaledPoints[i][2]);
		}

	}

	public Signature(Point[] points, Double maxEigenValue) {
		this.points = points;
		this.maxEigenValue = maxEigenValue;
	}

	private void ScaleTime(Signature sign) {
		double deltaTime_1 = this.points[this.points.length - 1].time - this.points[0].time;
		double deltaTime_2 = sign.points[sign.points.length - 1].time - sign.points[0].time;
		double koeffDeltaTime = deltaTime_2 / deltaTime_1;

		for (int i = 0; i < this.points.length; i++) {
			this.points[i].time = this.points[i].time * koeffDeltaTime;
		}
		double deltaTime_0 = this.points[0].time - sign.points[0].time;
		for (int i = 0; i < this.points.length; i++) {
			this.points[i].time = this.points[i].time - deltaTime_0;
		}
	}

	public void Scale(Signature sign) {
//		Max max = new Max();
//		Min min = new Min();
//		Abs abs = new Abs();
//		for (int i = 0; i < Point.getSize(); i++) {
//			double[] points_1 = new double[this.points.length];
//			for (int j = 0; j < points_1.length; j++) {
//				points_1[j] = this.points[j].get(i);
//			}
//
//			double[] points_2 = new double[sign.getPoints().length];
//			for (int j = 0; j < points_2.length; j++) {
//				points_2[j] = sign.getPoints()[j].get(i);
//			}
//
//			double max_1 = max.evaluate(points_1);
//			double min_1 = min.evaluate(points_1);
//			double max_2 = max.evaluate(points_2);
//			double min_2 = min.evaluate(points_2);
//
//			double koeffDelta = abs.value(min_1 - max_1) / abs.value(min_2 - max_2);
//			for (int j = 0; j < this.points.length; j++) {
//				this.points[j].set(i, this.points[j].get(i) / koeffDelta);
//			}
//		}

		double koeff = this.getMaxEigenValue() / sign.getMaxEigenValue();

		for (Point p : this.points) {
			for (int i = 0; i < Point.getSize(); i++) {
				p.set(i, p.get(i) * koeff);
			}
		}
	}

	public PolynomialSplineFunction[] Interpolation() {
		ArrayList<Double> X = new ArrayList<>();
		ArrayList<Double> Y = new ArrayList<>();
		ArrayList<Double> P = new ArrayList<>();
		ArrayList<Double> T = new ArrayList<>();

		Point prevPoint = this.points[0];
		X.add(prevPoint.x);
		Y.add(prevPoint.y);
		P.add(prevPoint.pressure);
		T.add(prevPoint.time);
		for (Point p : this.points) {
			if (prevPoint.time < p.time) {
				X.add(p.x);
				Y.add(p.y);
				P.add(p.pressure);
				T.add(p.time);
			}
			prevPoint = p;
		}

		double[] Xp = ArrayUtils.toPrimitive(X.toArray(new Double[X.size()]));
		double[] Yp = ArrayUtils.toPrimitive(Y.toArray(new Double[Y.size()]));
		double[] Pp = ArrayUtils.toPrimitive(P.toArray(new Double[P.size()]));
		double[] Tp = ArrayUtils.toPrimitive(T.toArray(new Double[T.size()]));

		SplineInterpolator interpolator = new SplineInterpolator();
		PolynomialSplineFunction[] funcs = {
			interpolator.interpolate(Tp, Xp),
			interpolator.interpolate(Tp, Yp),
			interpolator.interpolate(Tp, Pp)};
		return funcs;
	}

	public void Equals(Signature sign) {
		this.ScaleTime(sign);
//		this.Scale(sign);

		PolynomialSplineFunction[] funcs_1 = this.Interpolation();
		PolynomialSplineFunction[] funcs_2 = sign.Interpolation();

		double interval = (this.points[this.points.length - 1].time - this.points[0].time) / 1000;

		double[] sumInt_1 = {0, 0, 0};
		double[] sumInt_2 = {0, 0, 0};
		double[] sumDelInt = {0, 0, 0};

		RombergIntegrator integrator = new RombergIntegrator();
		Abs abs = new Abs();
		for (double i = this.points[0].time, j = this.points[0].time + interval;
				j < this.points[this.points.length - 1].time;
				i += interval, j += interval) {
			for (int k = 0; k < Point.getSize(); k++) {
				double int_1 = abs.value(integrator.integrate(1000000, funcs_1[k], i, j));
				double int_2 = abs.value(integrator.integrate(1000000, funcs_2[k], i, j));
				sumInt_1[k] += int_1;
				sumInt_2[k] += int_2;
				sumDelInt[k] += abs.value(int_1 - int_2);
			}
		}

		double[] per_1 = new double[Point.getSize()];
		double[] per_2 = new double[Point.getSize()];
		double[] perMean = new double[Point.getSize()];

		for (int i = 0; i < Point.getSize(); i++) {
			per_1[i] = sumInt_1[i] / sumDelInt[i];
			per_2[i] = sumInt_2[i] / sumDelInt[i];

			perMean[i] = (per_1[i] + per_2[i]) / 2;
		}
	}
}
